--[[
	service，处理 role， table 状态state
	最核心的是客户端输完入桌code后，返回给客户端应该去拿个游戏节点node
	这里的节点，在单游戏单节点的时候，和游戏是对应的，但当一个游戏有多个节点的时候
	可能会去多个节点，所以这里定义为 节点 ，感觉更有意义

	处理在线用户的所有状态
	主要接口：
	一，游戏节点调用，创建自定义桌子，返回一个唯一code， 自己存下 code vs 节点，桌子的对应关系
	二，游戏节点调用，删除取消某张桌子
	三，gate直接来查询，某 code  需要去哪个节点
	四，每个rid，进入，离开游戏的时候，都要修改状态
	四，每个rid，进入，离开桌子的时候，都要修改状态
	TODO:
	四，要求各个gamenode上报桌子情况（hall重启后，还可以再次获得管理列表）
	五，可以代建的时候，列出某些人待建的所有桌子
]]
local filename = "roletablestate.lua"
local skynet = require "skynet"
local cluster = require "skynet.cluster"
require "skynet.manager"
local string = string
local math = math

local filelog = require "filelog"
local timetool = require "timetool"
local tabletool = require "tabletool"
local redisdao = require "redisdao"
local json = require "cjson.safe"
json.encode_sparse_array(true, 1, 1)

local routerbase = require "routerbase"
require "pubenum"

local Roletablestate = routerbase:new()

-- 这里的数据都需要实时保存到redis里面，这样gate每次重启，都从里面拉数据
-- 最重要的数据是 codelist, onlinerids 里的在牌桌里玩家
-- 对每个用户都是重新登录，但在牌桌里的用户，还是可以再次进入游戏
-- 暂时只有 gmmqredis，所以顺手存放在这个redis里面了。

--[[
	入桌码列表 codelist，关键的数据结构，code为key,
	主要是为了桌子入桌，需要重启的时候全恢复
	code => {
		nodename=> "ddznode"
		table_index=> 123
		create_rid=> rid
		create_time => 123
	}
]]
local codelist = {}

--- 下面三个结构是一体的，同时改变
--[[
	游戏桌子索引列表， gametablelist，关键的数据结构，gamenode + table_index为key, 里面玩家列表是value
	主要是为了桌子聊天用
	ddznode_123 => {
		xxxrid = 1,
	}
]]
local gametablelist = {}

--[[
	在线玩家状态列表
	为了断线重启后，再次拉玩家入桌，重启需要恢复在桌子游戏里的玩家，
	玩家entergame的时候，在这里查询是否在这在线了吗。为了性能考虑，单个存放redis
	1213 => {rolename, sex}
	1234 => {rolename, sex, nodename, table_index, table_time, logo} table_time 入桌时间
]]
local onlinerids = {}

--[[
	在大厅的玩家，这里的key rid,这只是为了方便检索出空闲玩家
]]
local idlerids = {}

--[[
    key为：nodename .. "_" .. table_index
    nodename + table_index到table_code的映射表，gate重启时，方便查询已进桌玩家所在桌子是否还存在
]]
local nodeindex_to_code ={}

--[[
	封号玩家列表,在该列表的rid均处于封号状态
]]
local blockedlist = { }

--[[
	白名单列表：如果开启白名单限制，只有在白名单的玩家才能进入游戏
]]
local whitelist = { }
local whitelist_state = false    --白名单状态

---------------------------------------------------------------------------------

-- 生成一个在不在codelist的 code
-- 非零开头的6位， 一共是90万种可能
local function gencode()
	local newcode = math.random(100000,999999)
	while (codelist[newcode])
	do
		newcode = math.random(100000,999999)
	end
	return newcode
end

--  从gametablelist.allrid删除一个rid
local function removerid(tab, rid)
	for i,v in ipairs(tab) do
		if v == rid then  table.remove(tab, i) end
	end
end

-- 每次重启，从redis里面恢复在线状态，但要先做个时间判断
-- 如果距离上次写入时间超过5分钟，说明是维护，也不是断线重连了，就不恢复了
function Roletablestate.CMD.restore()
	--[[
	local result, result_data = redisdao.query_data(".gmmqredis", "get", "roletablestate_time")
	local write_time
	if result_data then
		write_time = tonumber(result_data)
	else
		write_time = 0
	end
	if timetool.get_time() - write_time > 300 then
		return true
	end
	skynet.error("-->Restore role table online state")

	result, result_data = redisdao.query_data(".gmmqredis", "get", "codelist")
	local tmpcodelist = json.decode(result_data) or {}

	for k,v in pairs(tmpcodelist) do
		codelist[tonumber(k)] = v
	end
	--]]

    local config_ab = skynet.call(".router", "lua", "get_gamenode_config_ab") or { }
	local nodenames = {"dn", "psz", "brnn", "ttz", "rb", "lhd", "hbsl", "ddz", "bjl", "bm", "fqzs", "sgj",}
	for k, v in ipairs(nodenames) do
        local nodename = v
        if config_ab[v] then
            nodename = nodename .. "nod" .. config_ab[v]
        else
            nodename = nodename .. "node"
        end
		local ok, nodedata = skynet.pcall(cluster.call, nodename, '.router', "get_gamenode_data")
		if ok and type(nodedata) == type{} and not nodedata.retcode then
			for code, data in pairs(nodedata) do
				data.nodename = nodename
				codelist[tonumber(code)] = data
                nodeindex_to_code[data.nodename .. "_" .. data.table_index] = tonumber(code)
			end
		end
	end

    --加载封号列表
    local result, result_data = redisdao.query_data(".gmmqredis", "get", "blockedlist")
	local tmplist = json.decode(result_data) or {}
	for k,v in pairs(tmplist) do
		blockedlist[tonumber(k)] = v
	end

    --加载白名单列表
    if Roletablestate.CMD.update_whitelist() then
        Roletablestate.CMD.change_whitelist_state(true)
    end
	
end

function write_redis(key, variable)
	redisdao.query_data(".gmmqredis", "set", key, json.encode(variable))
end

-- 游戏节点调用
-- 入参： 游戏节点名字，创建人，桌子的index，桌子的配置
-- 返回 一个六位的随机入桌码
function Roletablestate.CMD.create_table_code(gamenode, table_index, create_rid, conf)
	local code = gencode()
	while (codelist[code])
	do
		code = gencode()
	end
	codelist[code] = {
		nodename = gamenode,
		table_index = table_index,
		create_rid = rid,
		conf = conf,
		create_time = timetool.get_time()
	}
    nodeindex_to_code[gamenode .. "_" .. table_index] = code
	--write_redis("codelist", codelist)
	--redisdao.query_data(".gmmqredis", "set", "roletablestate_time", timetool.get_time())
	return code
end

function Roletablestate.CMD.gettablebycode( code )
	-- code = tonumber(code)
	if codelist[code] then
		local nodename = codelist[code].nodename
		if string.sub(nodename, -4, -2) == "nod" then
			nodename = string.sub(nodename, 1, -5)
		end
		return {nodename=nodename, realnode = codelist[code].nodename, table_index=codelist[code].table_index}
	else
		return {retcode=-1, retstr="房间不存在"}
	end
end

-- 入参: 游戏节点名字，创建人，桌子的index，
-- 如果桌子彻底删除时，在桌子中断线的人，agent的状态还是在桌子中， 需要重置他们的gamenode, table_index
-- 传入的参数 gamenode, table_index 暂时没用
function Roletablestate.CMD.del_table_code(code, gamenode, table_index)
	local c_gamenode = ""
	local c_table_index = 0
	local allrid = nil
    local gametable = nil

    if codelist[code] then
        c_gamenode = codelist[code].nodename or ""
        c_table_index = codelist[code].table_index or 0
        gametable = gametablelist[c_gamenode .. "_" .. c_table_index]
    end
    if gametable then
        allrid = gametable.allrid
    end

    nodeindex_to_code[c_gamenode .. "_" .. c_table_index] = nil
	gametablelist[c_gamenode .. "_" .. c_table_index] = nil
	codelist[code] = nil
	--write_redis("codelist", codelist)
	--redisdao.query_data(".gmmqredis", "set", "roletablestate_time", timetool.get_time())

	if allrid == nil or #allrid == 0 then return true end
	for _,rid in ipairs(allrid) do
		skynet.send( ".router", "lua", "role_leave_table", rid, c_gamenode, c_table_index, -1)
		Roletablestate.CMD.rid_leave_table(rid, c_gamenode, c_table_index)
	end
	return true
end

function Roletablestate.CMD.del_gamenode_code(gamenode)
	local codes = {}
	for k, v in pairs(codelist) do
		if v.nodename == gamenode then
			table.insert(codes, k)
            nodeindex_to_code[v.nodename .. "_" .. v.table_index] = nil
		end
	end
	for _, code in ipairs(codes) do
        codelist[code] = nil
	end
	return true
end

-- 在agent里面调用
function Roletablestate.CMD.rid_into_table(rid, gamenode, table_index)
	local key = gamenode .. "_" .. table_index
	if not gametablelist[key] then gametablelist[key] = {} end
	gametablelist[key][rid] = 1
	onlinerids[rid].nodename = gamenode
	onlinerids[rid].table_index = table_index
	onlinerids[rid].table_time = timetool.get_time()
	write_redis(rid .. ":intotable", onlinerids[rid])
	idlerids[rid] = nil
	return true
end

-- 在agent里面调用，先用这个保存的gamenode ，table_index 来做 leave
function Roletablestate.CMD.rid_leave_table(rid, gamenode, table_index, isoffline)
	onlinerids[rid] = onlinerids[rid] or { }
    gamenode = gamenode or ""
    table_index = table_index or 0
	if onlinerids[rid].nodename then
		gamenode = onlinerids[rid].nodename
	end
	if onlinerids[rid].table_index then
		table_index = onlinerids[rid].table_index
	end
	local key = gamenode .. "_" .. table_index
	if gametablelist[key] then
		gametablelist[key][rid] = nil
	end
	onlinerids[rid].nodename = nil
	onlinerids[rid].table_index = nil
	onlinerids[rid].table_time = nil
	redisdao.query_data(".gmmqredis", "del", rid .. ":intotable")
    idlerids[rid] = 1
    if isoffline then
        onlinerids[rid] = nil
        idlerids[rid] = nil
    end
	
	return true
end

-- 在agent里面调用，写入在线表
-- 然后去redis获取用户是否在桌游戏，这样gatenode重启的时候，也还可以获得，玩家可以直接重进桌
function Roletablestate.CMD.rid_entergame(rid, rolename, sex, logo, level)
	onlinerids[rid] = {rolename = rolename, sex = sex, logo = logo, level = level}
	idlerids[rid] = 1
	local result, result_data = redisdao.query_data(".gmmqredis", "get", rid .. ":intotable")
    local onlinestat = json.decode(result_data)

    result, result_data = redisdao.query_data(".gmmqredis", "get", rid .. ":startmatch")
    local match_data = json.decode(result_data)
    
    if onlinestat then
        local key = onlinestat.nodename .. "_" .. onlinestat.table_index
        if nodeindex_to_code[key] then
            onlinerids[rid].nodename = onlinestat.nodename
            onlinerids[rid].table_index = onlinestat.table_index
            onlinerids[rid].table_time = onlinestat.table_time
        else
		    redisdao.query_data(".gmmqredis", "del", rid .. ":intotable")
        end
    end

    onlinestat = onlinestat or { }
    if match_data then
        onlinestat.match_gamenode = match_data.match_gamenode
        onlinestat.match_tabletype = match_data.match_tabletype
    end

	return onlinestat
end

-- 在agent里面调用
function Roletablestate.CMD.rid_exit(rid)
	onlinerids[rid] = nil
	idlerids[rid] = nil
end

-- 给桌内发广播（聊天）消息的时候使用
function Roletablestate.CMD.get_table_allrid( gamenode, table_index )
    gamenode = gamenode or "" 
    table_index = table_index or 0
	local key = gamenode .. "_" .. table_index
	if(gametablelist[key]) then
		return tabletool.keys(gametablelist[key])
	end
	return {}
end

-- 获取在线人数
function Roletablestate.CMD.get_online_count(nodename)
	local i = 0
	for _,_ in pairs(onlinerids) do
		i = i + 1
	end
	return i
end

-- 获取在所有，da'ting各个游戏节点人数分布，返回一个table
function Roletablestate.CMD.get_online_state()
	local gategameinfo = {}
	local online_state = {all=0, hall=0, allrobot = 0, hallrobot = 0,}
	for k,v in pairs(onlinerids) do
		online_state.all = online_state.all + 1
		if not v.nodename and not v.match_gamenode then 
			online_state.hall = online_state.hall + 1
		end
        if v.level == 99 then
            online_state.allrobot = online_state.allrobot + 1
            if not v.nodename and not v.match_gamenode then
                online_state.hallrobot = online_state.hallrobot + 1
            end
        end
		if v.nodename or v.match_gamenode then
			local gamename = v.match_gamenode or string.match(v.nodename or "","(.+)node") or "gate"
			gategameinfo[gamename] = gategameinfo[gamename] or {real = 0, robot = 0}
            if v.level == 99 then
                gategameinfo[gamename].robot = gategameinfo[gamename].robot + 1
            else
                gategameinfo[gamename].real = gategameinfo[gamename].real + 1
            end
		end
	end
	
    --获取各个游戏机器人数
    --[[
    local config_ab = skynet.call(".router", "lua", "get_gamenode_config_ab") or { }
    local nodelist = {"dn", "psz", "brnn", "ttz", "rb", "lhd", "hbsl"}
    for _,v in pairs(nodelist) do
        local nodename = v
        if config_ab[v] then
            nodename = nodename .. "nod" .. config_ab[v]
        else
            nodename = nodename .. "node"
        end
        local status,robot_num = pcall(cluster.call, nodename, ".router", "get_game_robot_num")
        if status and robot_num then
            gategameinfo[v] = gategameinfo[v] or {real = 0, robot = 0}
            gategameinfo[v].robot = gategameinfo[v].robot + robot_num
        end
    end
    ]]
    online_state.ingame = online_state.all - online_state.hall
	online_state.gategameinfo = gategameinfo

	return online_state
end

-- 获取空闲玩家
-- idlerids 是个连续的，所以用随机数来截断，取 rolenum 个
function Roletablestate.CMD.get_idle_role(rolenum)
	local ridlist = {}
	local rolenum = rolenum or 10
	if rolenum <= 0 then rolenum = 10 end

	local idleridlist = tabletool.keys(idlerids)
    local start = 1
    if #idleridlist <= rolenum then
        rolenum = #idleridlist
    else
	    local endpos = #idleridlist - rolenum + 1
        start = math.random(1,endpos)
    end

	for i = start,#idleridlist do
		if idleridlist[i] and tonumber(idleridlist[i]) ~= 0 and onlinerids[idleridlist[i]] then
			table.insert(ridlist, {rid=idleridlist[i], rolename=onlinerids[idleridlist[i]].rolename, sex=onlinerids[idleridlist[i]].sex, logo = onlinerids[idleridlist[i]].logo})
            rolenum = rolenum -1
            if rolenum == 0 then
                break
            end
		end
	end
	return ridlist
end

--
--获得所有在线玩家
--
function Roletablestate.CMD.get_onlinerids()
    return onlinerids or {}
end

--开始匹配
function Roletablestate.CMD.rid_start_match(rid, gamenode, tabletype)
    if onlinerids[rid] then
        onlinerids[rid].match_gamenode = gamenode
        onlinerids[rid].match_tabletype = tabletype
    end
	write_redis(rid .. ":startmatch", { match_gamenode = gamenode, match_tabletype = tabletype})
	return true
end

--结束匹配
function Roletablestate.CMD.rid_end_match(rid, gamenode, tabletype)
    if onlinerids[rid] then
        onlinerids[rid].match_gamenode = nil
        onlinerids[rid].match_tabletype = nil
    end
	redisdao.query_data(".gmmqredis", "del", rid .. ":startmatch")
	return true
end

--
--从redis获得玩家所在桌子信息
--
function Roletablestate.CMD.get_rid_tableinfo(rid)
    if not rid or rid < 0 then
        return nil
    end

    --玩家在线，直接从onlinerids获得
    if onlinerids[rid] then
        return onlinerids[rid]
    end

    --玩家不在线，从redis获得
    local result, result_data = redisdao.query_data(".gmmqredis", "get", rid .. ":intotable")
    local onlinestat = json.decode(result_data)
    return onlinestat or { }
end

--
--封锁玩家账号
--
function Roletablestate.CMD.blocked_rid(rid)
    if not rid or rid < 1 then
        return false
    end
    blockedlist = blockedlist or { }
    blockedlist[rid] = os.date("%Y-%m-%d %H:%M:%S",timetool.get_time())
    write_redis("blockedlist", blockedlist)

    return true
end

--
--解封玩家
--
function Roletablestate.CMD.unblocked_rid(rid)
    if not rid or rid < 1 then
        return false
    end
    blockedlist = blockedlist or { }
    blockedlist[rid] = nil
    write_redis("blockedlist", blockedlist)

    return true
end

--
--检测玩家是否被封号
--
function Roletablestate.CMD.check_rid_isblocked(rid)
    if not rid or rid < 1 then
        return true
    end
    return blockedlist[rid]
end

--
--记录充值记录
--
function Roletablestate.CMD.write_chargerecord(rid, value, money, chargetype)
    local charge_type = tonumber(chargetype) or 0
    if not rid or not value then
        return
    end
    money = money or math.floor(value / 100) * 100

    local key = rid .. ":chargerecord"
    local result, result_data = redisdao.query_data(".gmmqredis", "get", key)
    local record = json.decode(result_data or "{}") or { }

    local curtime = timetool.get_time()
    local curday = os.date("%d", curtime)
    if record.record_time then
        local lastday = os.date("%d",tonumber(record.record_time))
        if lastday ~= curday then
            record.total_charge = 0
        end
    end
    record.record_time = curtime
    record.total_charge = (record.total_charge or 0) + money
    redisdao.query_data(".gmmqredis", "set", key, json.encode(record))

    --更新累积充值金额
    result = cluster.call("datanode", ".router", "select_rk", rid, "rs_money", true)
    if not result or result[1] == 0 then
        return false
    end

    local setargs = nil
    local rolemoney = result[2][rid]
    if charge_type == EChargeType.ECHARGE_TYPE_AGENCY 
    or charge_type == EChargeType.ECHARGE_TYPE_AGENCY_PRE then    --代理奖励转入
        setargs = {agency_charge = rolemoney.agency_charge + money}
    else    --正常充值
        setargs = {normal_charge = rolemoney.normal_charge + money}
    end
    
     --充值后，更新打码值
    if charge_type == EChargeType.ECHARGE_TYPE_AGENCY 
    or charge_type == EChargeType.ECHARGE_TYPE_AGENCY_PRE then
        --代理后台充值，不算充值
    elseif chargetype ~= nil then
        --正常充值，改变基础打码值,限额打码值
        setargs.base_chipvalue = tonumber(rolemoney.base_chipvalue) + value
        setargs.limit_chipvalue = tonumber(rolemoney.limit_chipvalue) + value
    end 
    
    skynet.pcall(cluster.send, "datanode", ".router", "update_rk", rid, "rs_money", 0, setargs)
end

--
--记录充今日输赢
--
function Roletablestate.CMD.write_winorloserecord(rid, value, gamename)
    value = tonumber(value) or 0
    if not rid or value == 0 then
        return
    end

    local key = nil
    if value > 0 then
        key = rid .. ":winrecord"
    else
        key = rid .. ":loserecord"
    end
    local result, result_data = redisdao.query_data(".gmmqredis", "get", key)
    local record = json.decode(result_data or "{}") or { }

    local curtime = timetool.get_time()
    local curday = os.date("%d", curtime)
    if record.record_time then
        local lastday = os.date("%d",tonumber(record.record_time))
        if lastday ~= curday then
            record.total_value = 0
        end
    end
    record.record_time = curtime
    record.total_value = (record.total_value or 0) + math.abs(value)
    redisdao.query_data(".gmmqredis", "set", key, json.encode(record))

    return true
end

--
--更新白名单,重新去redis读取
--
function Roletablestate.CMD.update_whitelist()
    local result, result_data = redisdao.query_data(".gmmqredis", "get", "whitelist")
    if result and result_data then
        local tmplist = json.decode(result_data) or {}
        if next(tmplist) ~= nil then
            for k,v in pairs(tmplist) do
		        whitelist[tonumber(k)] = v
	        end
            return true
        end
    end
	
    return false
end

--
--切换白名单状态：关闭或打开白名单
--
function Roletablestate.CMD.change_whitelist_state(newstate)
    whitelist_state = newstate
    local curstate = (whitelist_state and "true") or "false"
    redisdao.query_data(".gmmqredis", "set", "whitelist_state", curstate)
end

--
--检测玩家是否是否在白名单
--
function Roletablestate.CMD.check_rid_iswhitelist(rid)
    if not whitelist_state then
        return true
    end

    rid = tonumber(rid)
    if not rid or rid < 1 then
        return false
    end

    return whitelist[rid]
end

skynet.start(function ()
	Roletablestate:start()
end)
